/* eslint-disable no-plusplus,no-nested-ternary,no-mixed-operators */
const path = require('path');
const moment = require('moment');
const fs = require('fs-extra');
const { Types } = require('mongoose');
const compressing = require('compressing');
const probeImage = require('probe-image-size');
const { spawn } = require('child_process');
const ImageModule = require('docxtemplater-image-module-free');
const utility = require('../../../utility');

const classCreator = require('./../base-report-test-case-transformer');
const ParentReporter = require('./../default-report');
const files = require('./../../../files');
const functions = require('./../../../functions');
const tests = require('./../../../tests');
const logger = require('./../../../logger');
const TestCases = require('./../../../mongo/test-case');
const ProjectVersionSpliter = require('../../project-version-spliter');
const Docx = require('./../adapter/docx');
const FunctionRelations = require('../../../mongo/function-relation');
class DefaultReporter extends ParentReporter {
  async generateReport(template, data, output) {
    const images = new Map();
    Object.keys(data).forEach((key) => {
      if (key.endsWith('Image')) {
        images.set(data[key], key);
      }
    });
    output = `${output}.docx`;
    const templateIns = new Docx(path.join(__dirname, 'template', 'docx', `${template}.docx`));
    if (images.size > 0) {
      templateIns.getAdapterInstance().attachModule(new ImageModule({
        centered: false,
        getImage: (output) => fs.readFileSync(output),
        getSize: (img, tagValue) => {
          if (!images.has(tagValue)) {
            return [0, 0];
          }
          const imageKey = images.get(tagValue);
          if (!this.getImageSize()[imageKey]) {
            throw new Error(`Can not get image size of ${imageKey}`);
          }
          return [this.getImageSize()[imageKey].width, this.getImageSize()[imageKey].height];
        },
      }));
    }
    await templateIns.generate(
      data,
      output,
      true
    );
    return output;
  }

  /**
   * 报告生成
   */
  generate(progressIndicator) {
    const [filesProgressIndicator, summaryProgressIndictor] = progressIndicator.split([0.5, 0.5]);
    const result = {
      cachePath: this.outputDir,
    };
    this.imagesSize = {};
    this.images = {};
    this.filesFuncsFlowData = [];
    return new Promise((resolve, reject) => {
      this.spliteByFile(null, filesProgressIndicator)
        .then(() => this.generateSummary(summaryProgressIndictor))
        .then(() => this.generateDetailedDesign())
        .then(() => {
          const fileName = `${this.project.projectName}-${this.version.versionName}.${this.getReportName()}.zip`;
          result.reportPath = path.join(this.outputDir, fileName);
          const reportsDir = path.join(this.outputDir, this.project.projectName);
          return compressing.zip.compressDir(reportsDir, result.reportPath);
        })
        .then(() => {
          resolve(result);
        })
        .catch(reject);
    });
  }

  getVersion() {
    const toolInfo = require('../../../../../package.json');
    return toolInfo.version;
  }

  willTestStatements() {
    return this.version.coverages & tests.coverageKind.statement;
  }

  willTestBranch() {
    return this.version.coverages & tests.coverageKind.branch;
  }

  willTestDecision() {
    return this.version.coverages & tests.coverageKind.decision;
  }

  willTestCondition() {
    return this.version.coverages & tests.coverageKind.condition;
  }

  willTestMcdc() {
    return this.version.coverages & tests.coverageKind.mcdc;
  }

  async generateSummary(summaryProgressIndictor) {
    const summary = {
      reportTime: moment().format('YYYY年MM月DD日'),
      reportName: this.getReportName(),
      version: this.getVersion(),
      projectName: this.project.projectName,
      creator: this.creator.username,
    };
    await super.handleSummary(summary);
    let testedFuncsCount = 0;
    let untestedFuncsCount = 0;
    const funcOverviewGroupByFile = {};
    const funcNameMap = {};
    // 将函数按照文件进行分组
    summary.functionsOverview.forEach((func) => {
      funcNameMap[func._id.toString()] = func.functionName;
      if (func.testCount) {
        testedFuncsCount += 1;
      } else {
        untestedFuncsCount += 1;
      }
      func.coverageInfo.forEach((c) => {
        if (c.coverage === '-') {
          c.coverage = '无';
        }
      });
      if (!funcOverviewGroupByFile[func.fileId.toString()]) {
        funcOverviewGroupByFile[func.fileId.toString()] = {
          fileId: func.fileId.toString(),
          fileName: func.fileName,
          path: func.filePath,
          testCount: 0,
          coverageCount: summary.enabledCoverageKind.length,
          funcs: [],
        };
      }
      funcOverviewGroupByFile[func.fileId.toString()].testCount += func.testCount;
      func.idx = funcOverviewGroupByFile[func.fileId.toString()].funcs.length + 1;
      funcOverviewGroupByFile[func.fileId.toString()].funcs.push(func);
    });
    summary.functionsOverview = [];
    summary.testsWithError = [];
    summary.unCovered = [];
    await utility.arrayChunkOperation(Object.values(funcOverviewGroupByFile), 1, async (item, idx) => {
      item.idx = idx + 1;
      let loop = 1;
      const unCoveredInfo = [];
      await utility.arrayChunkOperation(item.funcs, 1, async (func) => {
        const cfg = await functions.getFunctionCFG(func._id);
        // 语句未覆盖情况
        if (this.willTestStatements() && cfg.nodes) {
          Object.values(cfg.nodes).forEach((node) => {
            if (!node.testIds || Object.keys(node.testIds).length === 0) {
              unCoveredInfo.push({
                idx: loop++,
                funcName: func.functionName,
                location: `${node.begin.line}-${node.end.line}`,
                kind: '语句',
                desc: '无',
              });
            }
          });
        }
        // 分支未覆盖情况
        if (this.willTestBranch() && cfg.branches) {
          Object.keys(cfg.branches).forEach((branchId) => {
            Object.keys(cfg.branches[branchId].branches).forEach((block) => {
              const { testIds, kind, value } = cfg.branches[branchId].branches[block];
              if (!testIds || Object.keys(testIds).length === 0) {
                const node = cfg.nodes[cfg.branches[branchId].nodeId];
                unCoveredInfo.push({
                  idx: loop++,
                  funcName: func.functionName,
                  location: node ? `${node.begin.line}-${node.end.line}` : '',
                  kind: '分支',
                  desc: kind === 'BOOLEAN' ? (value === '1' ? 'T' : 'F') : (kind === 'DEFAULT' ? kind : value),
                });
              }
            });
          });
        }
        // 条件未覆盖情况
        if (this.willTestCondition() && cfg.decisions) {
          Object.values(cfg.decisions).forEach((decision) => {
            Object.keys(decision.conditions).forEach((conditionId) => {
              const node = cfg.nodes[decision.nodeId];
              let TCovered = false;
              let FCovered = false;
              if (decision.coveredConditions) {
                Object.keys(decision.coveredConditions).forEach((truthTable) => {
                  if (truthTable[conditionId] === '1') {
                    TCovered = true;
                  } else if (truthTable[conditionId] === '0') {
                    FCovered = true;
                  }
                });
              }
              if (!TCovered) {
                unCoveredInfo.push({
                  idx: loop++,
                  funcName: func.functionName,
                  location: node ? `${node.begin.line}-${node.end.line}` : '',
                  kind: '条件',
                  desc: `T-${decision.conditions[conditionId]}`,
                });
              }
              if (!FCovered) {
                unCoveredInfo.push({
                  idx: loop++,
                  funcName: func.functionName,
                  location: node ? `${node.begin.line}-${node.end.line}` : '',
                  kind: '条件',
                  desc: `F-${decision.conditions[conditionId]}`,
                });
              }
            });
          });
        }
        // 决策未覆盖情况
        if (this.willTestDecision() && cfg.nodes) {
          // 节点判断部分
          Object.values(cfg.nodes).forEach((node) => {
            const { testIds, kind, exitPreNode } = node;
            let decisionStatistic = false;
            // 入口节点算或者所有连接结束节点的节点
            if (kind === functions.cfgNodeKind.entry || exitPreNode) {
              decisionStatistic = true;
            }
            if (decisionStatistic) {
              if (!testIds || Object.keys(testIds).length === 0) {
                unCoveredInfo.push({
                  idx: loop++,
                  funcName: func.functionName,
                  location: `${node.begin.line}-${node.end.line}`,
                  kind: '决策',
                  desc: exitPreNode ? '出口' : '入口',
                });
              }
            }
          });
          // decisions判断部分
          if (cfg.decisions) {
            Object.values(cfg.decisions).forEach((decision) => {
              const node = cfg.nodes[decision.nodeId];
              let TCovered = false;
              let FCovered = false;
              if (decision.coveredConditions) {
                Object.keys(decision.coveredConditions).forEach((truthTable) => {
                  if (truthTable[truthTable.length - 1] === '1') {
                    TCovered = true;
                  } else if (truthTable[truthTable.length - 1] === '0') {
                    FCovered = true;
                  }
                });
              }
              if (!TCovered) {
                unCoveredInfo.push({
                  idx: loop++,
                  funcName: func.functionName,
                  location: node ? `${node.begin.line}-${node.end.line}` : '',
                  kind: '决策',
                  desc: 'T',
                });
              }
              if (!FCovered) {
                unCoveredInfo.push({
                  idx: loop++,
                  funcName: func.functionName,
                  location: node ? `${node.begin.line}-${node.end.line}` : '',
                  kind: '决策',
                  desc: 'F',
                });
              }
            });
          }
        }
        // mcdc未覆盖情况
        if (this.willTestMcdc() && cfg.decisions) {
          Object.values(cfg.decisions).forEach((decision) => {
            if (!decision.cases) {
              return;
            }
            const node = cfg.nodes[decision.nodeId];
            // 算法一
            if (this.version.mcdcKind === tests.mcdcKind.masking) {
              Object.values(decision.cases).forEach((caseItem) => {
                if (!caseItem.testIds || Object.keys(caseItem.testIds).length === 0) {
                  unCoveredInfo.push({
                    idx: loop++,
                    funcName: func.functionName,
                    location: node ? `${node.begin.line}-${node.end.line}` : '',
                    kind: 'MC/DC',
                    desc: caseItem.condKey.split('').reverse().map((i, idx) => {
                      let str = 'x';
                      if (i === '0') {
                        str = 'f';
                      } else if (i === '1') {
                        str = 't';
                      }
                      if (idx === 0) {
                        str += '-';
                      }
                      return str.toUpperCase();
                    }).join(''),
                  });
                }
              });
              // 算法二
            } else if (decision.conditionCoveredPairs) {
              Object.keys(decision.conditionCoveredPairs).forEach((conditionId) => {
                let covered = false;
                decision.conditionCoveredPairs[conditionId].forEach(([i, j]) => {
                  if (covered) {
                    return;
                  }
                  if (!utility.isEmpty(decision.cases[i].testIds)
                    && !utility.isEmpty(decision.cases[j].testIds)
                  ) {
                    covered = true;
                  }
                });
                if (!covered) {
                  unCoveredInfo.push({
                    idx: loop++,
                    funcName: func.functionName,
                    location: node ? `${node.begin.line}-${node.end.line}` : '',
                    kind: 'MC/DC',
                    desc: decision.conditions[conditionId],
                  });
                }
              });
            }
          });
        }
      });
      if (unCoveredInfo.length > 0) {
        summary.unCovered.push({ idx: summary.unCovered.length + 1, path: item.path, items: unCoveredInfo });
      }
      // 执行异常用例
      const testsWithErrorArr = [];
      const testcases = await tests.getTests({}, {
        fileId: Types.ObjectId(item.fileId),
        resultKind: {
          $in: [
            tests.resultKind.failed,
            tests.resultKind.runtimeError,
            tests.resultKind.timeout,
            tests.resultKind.compileError,
            tests.resultKind.instrumentError,
            tests.resultKind.systemError,
          ],
        },
      });
      loop = 1;
      const typeSystem = await files.getTypes(item.fileId);
      const variables = await functions.getVariablesOfFile(item.fileId, this.version._id);
      const variablesMap = {};
      variables.forEach((v) => {
        variablesMap[v.functionId.toString()] = {
          output: v.output || {},
        };
      });
      testcases.forEach((testWithError) => {
        testWithError = testWithError.toObject();
        if (testWithError.resultKind === tests.resultKind.failed) {
          if (!variablesMap[testWithError.functionId.toString()]) {
            return;
          }
          const ReportTestCaseTransformer = classCreator();
          const transformer = new ReportTestCaseTransformer(
            tests.getJson(testWithError, this.i18n),
            variablesMap[testWithError.functionId.toString()],
            typeSystem,
            this.i18n
          );
          try {
            transformer.transform(this.complete);
          } catch (error) {
            logger.error(`testCase transform error: test case: ${testWithError._id}:`, error);
            return;
          }
          if (utility.isArrayType(transformer.output)) {
            transformer.output.forEach((o) => {
              if (o.value !== '' && o.value !== o.result) {
                testsWithErrorArr.push({
                  idx: loop++,
                  desc: `${o.variable}, 实际值=${o.result}, 期望值=${o.value}`,
                  kind: 0,
                  funcName: funcNameMap[testWithError.functionId.toString()],
                  id: `TC_${funcNameMap[testWithError.functionId.toString()]}_${testWithError.number}`,
                });
              }
            });
          }
        } else {
          testsWithErrorArr.push({
            idx: loop++,
            desc: testWithError.log && testWithError.log.location
              ? `错误位置：${testWithError.log.location.file} ${testWithError.log.location.line}行${testWithError.log.location.column}列`
              : '',
            kind: (testWithError.resultKind === tests.resultKind.runtimeError || testWithError.resultKind === tests.resultKind.timeout) ? 1 : 2,
            funcName: funcNameMap[testWithError.functionId.toString()],
            id: `TC_${funcNameMap[testWithError.functionId.toString()]}_${testWithError.number}`,
          });
        }
      });
      if (testsWithErrorArr.length > 0) {
        summary.testsWithError.push({ idx: summary.testsWithError.length + 1, path: item.path, items: testsWithErrorArr });
      }
      summary.functionsOverview.push(item);
      if (summaryProgressIndictor) {
        summaryProgressIndictor.advance(1 / Object.keys(funcOverviewGroupByFile).length);
      }
    });
    summary.testedFuncsCount = testedFuncsCount;
    summary.untestedFuncsCount = untestedFuncsCount;
    summary.executedTestCount = summary.testsOverview.succeed
      + summary.testsOverview.nonStrictSucceed
      + summary.testsOverview.failed
      + summary.testsOverview.exception
      + summary.testsOverview.codeDefect
      + summary.testsOverview.noResult;
    summary.succeedTestCount = summary.testsOverview.succeed
      + summary.testsOverview.nonStrictSucceed
      + summary.testsOverview.noResult;
    summary.executedTestCoverage = `${Math.round(summary.executedTestCount / summary.overview.testCount * 10000) / 100}%`;
    summary.testPassedCoverage = `${Math.round(summary.succeedTestCount / summary.executedTestCount * 10000) / 100}%`;
    summary.coverageInfo = summary.enabledCoverageKind.map((item) => ({
      name: item.name,
      coverage: summary.overview[item.coverageKey],
      covered: summary.overview[item.coveredKey],
      total: summary.overview[item.totalKey],
    }));
    summary.testResultStatistic = await this.getTestResultStatistic(this.versionId);
    summary.sourceFilesOverview.forEach((file, index) => {
      file.idx = index + 1;
      if (!summary.testResultStatistic[file._id.toString()]) {
        file.testStatistics = '0/0/0/0';
      } else {
        const testStatistics = summary.testResultStatistic[file._id.toString()];
        const executed = file.testCount - (testStatistics[tests.resultKind.unknown] || 0);
        const exception = (testStatistics[tests.resultKind.systemError] || 0)
          + (testStatistics[tests.resultKind.runtimeError] || 0)
          + (testStatistics[tests.resultKind.timeout] || 0)
          + (testStatistics[tests.resultKind.instrumentError] || 0)
          + (testStatistics[tests.resultKind.compileError] || 0);
        file.testStatistics = `${executed}/${exception}/${testStatistics[tests.resultKind.failed] || 0}/${file.testCount}`;
      }
      file.coverageInfo.forEach((c) => {
        if (c.coverage === '-') {
          c.coverage = '无';
        }
      });
    });
    return this.generateSummaryReport(
      summary,
      path.join(
        this.outputDir,
        this.project.projectName,
        this.version.versionName,
        this.project.projectName,
      )
    );
  }

  async getTestResultStatistic(versionId) {
    const ret = await TestCases.aggregate([
      {
        $match: {
          versionId: Types.ObjectId(versionId),
        },
      },
      {
        $lookup: {
          from: 'functions',
          localField: 'functionId',
          foreignField: '_id',
          as: 'function',
        },
      },
      {
        $unwind: '$function',
      },
      {
        $match: {
          'function.collectCoverage': true,
        },
      },
      {
        $group: {
          _id: {
            fileId: '$fileId',
            resultKind: '$resultKind',
          },
          count: { $sum: 1 },
        },
      },
    ]);
    const statistic = {};
    ret.forEach((item) => {
      const { fileId, resultKind } = item._id;
      if (!statistic[fileId.toString()]) {
        statistic[fileId.toString()] = {};
      }
      statistic[fileId.toString()][resultKind] = item.count;
    });
    return statistic;
  }

  async handleFile(file, versionDir) {
    this.versionDir = versionDir;
    const reportPath = `${path.join(versionDir, path.dirname(file.path), `${this.project.projectName}_${file.fileName}`)}`;
    const res = await ProjectVersionSpliter.prototype.handleFile.call(this, file, versionDir);
    const { typeSystem, functions: funcs, resultCount } = res;
    const fileFuncFlowData = {
      fileName: file.fileName,
      functions: [],
    };
    const fileCommentsMap = new Map();
    const fileComments = await files.read502FileComment(file._id);
    fileComments.forEach((fileComment) => {
      fileCommentsMap.set(fileComment.line, fileComment.comment.split('\n').map((str) => ({ comment: str })));
    });
    await utility.arrayChunkOperation(funcs, 100, async (func) => {
      if (!func.tests || func.tests.length === 0) {
        return;
      }
      func.fileName = file.fileName;
      const input = {
        params: '无',
        globals: '无',
      };
      const output = {
        globals: '无',
        returnValue: '无',
      };
      // 上层调用函数
      let callersFuncs = '无';
      // 下层调用函数
      let calleesFuncs = [];
      const funcVariables = func.variables || {};
      if (funcVariables.variables) {
        if (funcVariables.variables.params && !utility.isEmpty(funcVariables.variables.params)) {
          input.params = Object.keys(funcVariables.variables.params).toString();
        }
        if (funcVariables.variables.globals && !utility.isEmpty(funcVariables.variables.globals)) {
          input.globals = Object.keys(funcVariables.variables.globals).toString();
        }
      }
      if (funcVariables.output) {
        if (funcVariables.output.global && !utility.isEmpty(funcVariables.output.global)) {
          output.globals = Object.keys(funcVariables.output.global).toString();
        }
        if (funcVariables.output['%'] && funcVariables.output['%']['@type']) {
          output.returnValue = `${funcVariables.output['%']['@type']}`;
        }
      }
      calleesFuncs = (await FunctionRelations.find({ fromFuncId: func._id }, ['toFuncName']).lean())
        .map((item) => item.toFuncName);
      const callersFuncIds = (await FunctionRelations.find({ toFuncId: func._id }, ['fromFuncId']).lean())
        .map((item) => item.fromFuncId);
      if (callersFuncIds.length > 0) {
        callersFuncs = (await functions.getFunctions(func.versionId, { functionIds: callersFuncIds }))
          .map((item) => item.functionName).toString();
      }
      const flowChartData = await functions.get502DesignFlowData(func._id, fileComments);
      await this.generateFuncsFlowChart(func, flowChartData);
      fileFuncFlowData.functions.push({
        index: func.index,
        functionName: func.functionName,
        funcFlowImage: func.funcFlowImage,
        functionFullName: `${output.returnValue === '无' ? 'void' : output.returnValue} ${func.functionFullName}`,
        input,
        output,
        callersFuncs,
        calleesFuncs: calleesFuncs.length === 0 ? '无' : calleesFuncs.toString(),
        comments: fileCommentsMap.has(func.index) ? fileCommentsMap.get(func.index) : [],
      });
    });
    if (fileFuncFlowData.functions.length !== 0) {
      fileFuncFlowData.functions.sort((a, b) => a.index - b.index);
      this.filesFuncsFlowData.push(fileFuncFlowData);
    }
    if (!resultCount) {
      return Promise.resolve();
    }
    await fs.ensureDir(path.dirname(reportPath));
    funcs.sort((a, b) => a.index - b.index);
    const funcsDetails = this.getFunctionDetailsOfFile(typeSystem, funcs);
    const outputPath = await this.generateFileReport(
      {
        fileName: file.fileName,
        filePath: file.path,
        summary: DefaultReporter.getSummaryOfFile(funcs),
        functions: funcsDetails,
      },
      reportPath
    );
    return { reportPath: outputPath, funcsDetails };
  }

  async generateDetailedDesign() {
    if (this.filesFuncsFlowData.length === 0) {
      return;
    }
    let funcIndex = 1;
    this.filesFuncsFlowData.forEach((fileFuncsFlowData) => {
      fileFuncsFlowData.functions.forEach((func) => {
        func.funcIndex = funcIndex;
        funcIndex += 1;
      });
    });
    const reportPath = path.join(this.versionDir, '详细设计报告');
    await this.generateReport('detailed-design-template',
      {
        files: this.filesFuncsFlowData,
        ...this.images,
      },
      reportPath);
  }

  getFunctionDetailsOfFile(typeSystem, functions) {
    const resFunctions = [];
    let idx = 0;
    functions.forEach((func) => {
      if (!func.tests.length) {
        return;
      }
      const mallocs = func.variables.malloc || {};
      const stubs = func.variables.stubs || {};
      const userDefinedVariables = [
        // malloc
        ...Object.keys(mallocs).map((key) => ({ name: `${mallocs[key]['@type']} ${key}` })),
        // 手动创建桩函数
        ...Object.values(stubs).filter((stub) => stub.stubsTriggerKind === 2).map((stub) => {
          const returnType = stub['%'] ? stub['%']['@type'] : 'void';
          const paramsString = stub.params.map((param) => `${param['@type']} ${param['@name']}`).join(', ');
          return {
            name: `${returnType} ${stub['@name']}(${paramsString})`,
          };
        }),
      ];
      const stubDeclarations = Object.values(stubs).map((stub) => {
        const paramsString = stub.params.map((param) => `${param['@type']} ${param['@name']}`).join(', ');
        const returnType = stub['%'] ? stub['%']['@type'] : 'void';
        return {
          name: `${returnType} ${stub['@name']}(${paramsString})`,
        };
      });
      const funcTests = func.tests.reduce((obj, tc) => {
        obj[tc._id.toString()] = tc;
        return obj;
      }, {});
      const tests = this.getTestDetailsOfFunction(typeSystem, func);
      tests.forEach((tc) => {
        const testCase = funcTests[tc.testId.toString()];
        tc.execCount = testCase.execCount;
        ['invokedFunc',
          'invoke',
          'statement',
          'branch',
          'mcdc',
          'condition',
          'decision'].forEach((key) => {
          if (!tc[`${key}Coverage`]) {
            return;
          }
          if (tc[`${key}Coverage`] === '-') {
            tc[`${key}Coverage`] = '无';
          } else {
            const covered = tc[`${key}Coverage`] === '0%' ? 0 : testCase[`${key}Covered`];
            const total = func[`${key}Total`];
            tc[`${key}Coverage`] += `(${covered}/${total})`;
          }
        });
        const { input, output } = tc.detail;
        const stubs = {};
        [input, output].forEach((io, idx) => {
          const scope = idx === 0 ? 'input' : 'output';
          let currentCategory;
          let stub;
          io.forEach((item) => {
            item.scope = scope;
            if (item.category) {
              currentCategory = item.category;
            }
            if (item.category === '返回值') {
              tc.detail.outputValue = item.value || '无';
              tc.detail.outputResult = item.result || '无';
            }
            if (currentCategory === '桩函数' && !item.variable.startsWith(' ')) {
              stub = item.variable;
            }

            if (item.value) {
              item.value = ` = ${item.value}`;
            }

            item.currentCategory = currentCategory;
            if (currentCategory === '桩函数') {
              stubs[stub] = stubs[stub] || {
                stubName: stub,
                input: [],
                output: [],
              };
              const reg = /\s[^\s^(]+\s*\(/;
              const ret = reg.exec(stub);
              if (ret) {
                stubs[stub].stubKey = ret[0].replace(/[\s(]/g, '');
              }
              if (item.variable.startsWith(' ')) {
                stubs[stub][scope].push(item);
              }
            }
          });
        });

        tc.detail.stubs = Object.values(stubs)
          .filter((stub) => !stub.stubName.startsWith('第')); // 组出来的数据会有冗余
        const inputOthers = [];
        const inputGlobals = [];
        const inputParams = [];
        let arr;
        input.forEach((vobj) => {
          switch (vobj.currentCategory) {
            case '桩函数':
            case '返回值':
              return;
            case '全局变量':
              arr = inputGlobals;
              break;
            case '形参':
              arr = inputParams;
              break;
            default:
              arr = inputOthers;
              break;
          }
          if (vobj.category && vobj.variable) {
            arr.push({
              category: `${vobj.category}:`,
              value: '',
              variable: '',
            });
            arr.push({
              category: '',
              value: vobj.value,
              variable: vobj.variable,
            });
          } else {
            arr.push(vobj);
          }
        });
        tc.detail.input = inputOthers;
        tc.detail.inputGlobals = inputGlobals;
        tc.detail.inputParams = inputParams;

        // console.log(JSON.stringify(tc.detail, null, 1));
      });
      idx += 1;
      resFunctions.push({
        index: idx,
        functionName: func.functionName,
        description: func.description || '',
        udfs: func.udfs,
        codeDefects: this.getCodeDefects(func),
        summary: this.getSummaryOfFunction(func),
        tests,
        integrationFunctions: func.integrationFunctions,
        integrationFunctionsCount: func.integrationFunctions.length,
        userDefinedVariables,
        stubDeclarations,
        funcFlowImage: func.funcFlowImage,
      });
    });
    return resFunctions;
  }

  getImageSize() {
    return this.imagesSize;
  }

  /**
 *  mermaid-cli 生成图片
 * @param {String} inputPath  输入xxx.md格式的图表文件，具体格式npx mmdc -h
 * @param {String} outputPath 输出路径
 * @param {String} fileName 输出文件名
 * @param {String} extname  输出的文件扩展名
 * @param {String} key  标记图片的key
 */
  async generateFlowImage(inputFilePath, outputPath, fileName, extname, key) {
    const mmdcpath = `"${path.join(__dirname, '../', '../', '../', '../', '../', 'node_modules', '.bin', 'mmdc')}"`;
    const puppeteerConfig = `"${path.join(__dirname, 'puppeteer-config.json')}"`;
    const styleConfig = `"${path.join(__dirname, 'style.css')}"`;
    await new Promise((resolve, reject) => {
      const mmdc = spawn(mmdcpath, [
        '-i', inputFilePath,
        '-o', path.join(outputPath, `${fileName}.${extname}`),
        '-p', puppeteerConfig,
        '-C', styleConfig,
      ], { shell: true });
      mmdc.stderr.on('data', (data) => {
        logger.info(`mmdc error: ${data}`);
        reject(data);
      });
      mmdc.on('error', (error) => {
        logger.info(`mmdc error: ${error}`);
        reject(error);
      });
      mmdc.on('close', (code) => {
        logger.info(`mmdc generate ${key} ${extname}:${code === 0 ? 'success' : 'failed'}`);
        resolve();
      });
    });
    // rename output filename
    await new Promise((resolve, reject) => {
      const oldFileName = path.join(outputPath, `${fileName}-1.${extname}`);
      const newFileName = path.join(outputPath, `${fileName}.${extname}`);
      fs.rename(oldFileName, newFileName, (err) => {
        if (err) {
          reject(new Error(`rename file name:${oldFileName} error:${err}`));
        }
        resolve();
      });
    });
    // delete input xxxx.md
    await new Promise((resolve, reject) => {
      fs.unlink(inputFilePath, (err) => {
        if (err) {
          reject(new Error(`delete input ${inputFilePath} error:${err}`));
        }
        resolve();
      });
    });
  }

  /**
   *
   * @param {Object} flowChartData 图数据
   * @param {Object} func 函数数据
  {
      "nodes":[
        {
          "id":"n1",
          "type:":"Decision", // Start,End,Process,Decision,
          "label":""
        },
        {
          "id":"n2",
          "type:":"Process",
          "label":""
        }
      ],
      "edges":[{
        "id":"e1",
        "label":"",
        "from":"n1",
        "to":"n2"
      }]
    }
   */
  async generateFuncsFlowChart(func, flowChartData) {
    if (!flowChartData.nodes || !flowChartData.edges) {
      return;
    }
    if (flowChartData.nodes) {
      flowChartData.nodes = flowChartData.nodes.map((node) => {
        switch (node.type) {
          case 'Start':
          case 'End': {
            node.label = node.label ? `(("${node.label}"))` : '(( ))';
            break;
          }
          case 'Process': {
            node.label = node.label ? `["${node.label}"]` : '[ ]';
            break;
          }
          case 'Decision': {
            node.label = node.label ? `{"${node.label}"}` : '{ }';
            break;
          }
          default: break;
        }
        return node;
      });
    }
    if (flowChartData.edges) {
      flowChartData.edges = flowChartData.edges.map((edge) => {
        if (edge.label) {
          edge.label = `|"${edge.label}"|`;
        }
        return edge;
      });
    }
    const functionId = func._id.toString();
    const outputPath = path.join(this.outputDir, functionId);
    const mdFilePath = `${outputPath}.md`;
    const imageFilePath = `${outputPath}.png`;
    const markdownFileData = await utility.getDotTemplate(path.join(__dirname, 'template', 'dot', 'function-flow-chart-template.md.dot'), flowChartData);
    await utility.writeFile(markdownFileData, mdFilePath);
    await this.generateFlowImage(mdFilePath, this.outputDir, functionId, 'png', `${func.fileName}:${func.functionName}`);
    const imageInfo = await probeImage(fs.createReadStream(imageFilePath));
    func.funcFlowImage = imageFilePath;
    const imageKey = `_${functionId}Image`;
    const imageHeightMax = 800;
    const imageWithMax = 600;
    this.imagesSize[imageKey] = {
      width: imageInfo.width && imageInfo.width < imageWithMax ? imageInfo.width : imageWithMax,
      height: imageInfo.height && imageInfo.height < imageHeightMax ? imageInfo.height : imageHeightMax,
    };
    this.images[imageKey] = imageFilePath;
  }
}
/**
 * Export model definition object.
 */
module.exports = DefaultReporter;
